package ldh.im.fxgui.component.message;

import com.google.common.eventbus.Subscribe;
import javafx.animation.FadeTransition;
import javafx.beans.Observable;
import javafx.beans.binding.DoubleBinding;
import javafx.collections.FXCollections;
import javafx.collections.ObservableList;
import javafx.collections.transformation.FilteredList;
import javafx.collections.transformation.SortedList;
import javafx.concurrent.Task;
import javafx.geometry.Pos;
import javafx.scene.Node;
import javafx.scene.control.Label;
import javafx.scene.control.ScrollBar;
import javafx.scene.control.ScrollPane;
import javafx.scene.input.ScrollEvent;
import javafx.scene.layout.*;
import javafx.util.Duration;
import ldh.im.fxbase.data.event.GroupUserNameChange;
import ldh.im.fxbase.data.pojo.ImMessage;
import ldh.im.fxbase.data.pojo.ImSession;
import ldh.im.fxbase.data.pojo.ImUser;
import ldh.im.fxbase.util.FxThreadUtil;
import ldh.im.fxbase.util.ThreadPoolUtil;
import ldh.im.fxgui.handle.IMessageEditorHandler;
import ldh.im.fxgui.component.message.bo.WrapImMessage;
import ldh.im.fxgui.service.MessageReadService;
import org.fxmisc.flowless.Cell;
import org.fxmisc.flowless.VirtualFlow;
import org.fxmisc.flowless.VirtualizedScrollPane;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import java.util.*;
import java.util.concurrent.ConcurrentHashMap;
import java.util.function.Supplier;
import java.util.stream.Collectors;

public class MessageVirtualFlowView extends BaseMessageListViewHandler {

    private static final Logger LOGGER = LoggerFactory.getLogger(MessageVirtualFlowView.class);

    private ObservableList<WrapImMessage> items = FXCollections.observableArrayList();
    private FilteredList<WrapImMessage> filteredData = new FilteredList<>(items, s -> true);
    private SortedList<WrapImMessage> sortedList = new SortedList<WrapImMessage>(filteredData, this::messageComparator);
    private VirtualFlow<WrapImMessage, ?> messageList;
    private VirtualizedScrollPane virtualizedScrollPane;

    private Label loadPreDataLabel = new Label("正在加载数据。。。");
    private Label loadLastDataLabel = new Label("正在加载数据。。。");

    private Map<String, Cell> cellMap = new ConcurrentHashMap<>();
    private boolean isFrontEnd = false;    // 是否加载数据到头了
    private boolean isLastedEnd = false;   // 是否加载数据到尾了

    public MessageVirtualFlowView(IMessageEditorHandler messageEditorService, StackPane dialogRoot) {
        super(messageEditorService, dialogRoot);
        loadPreDataLabel.setVisible(false);
        loadPreDataLabel.setStyle("-fx-padding: 2 0 0 0");
        loadLastDataLabel.setVisible(false);
        loadLastDataLabel.setStyle("-fx-padding: 0 0 2 0");
    }

    public Node createMessagePaneNode() {
        messageList = VirtualFlow.createVertical(sortedList, m->{
            Cell<WrapImMessage, Node> cell = cellMap.get(m.getImMessage().getClientId());
            if (cell != null) {
                return cell;
            } else {
//                Pane pane = createMessagePane(m);
//                cell = Cell.wrapNode(pane);
                cell = new Cell() {
                    MessageView pane = (MessageView) createMessagePane(m);

                    @Override
                    public Node getNode() {
                        if (pane == null) {
                            pane = (MessageView) createMessagePane(m);
                        }
                        return pane;
                    }

                    @Override
                    public void updateItem(Object item) {
                        if (pane != null) {
                            pane.update();
                        }
                    }

                    @Override
                    public void reset() {
                        if (pane != null) {
                            pane.reset();
                        }
                    }
                };
                cellMap.put(m.getImMessage().getClientId(), cell);
                return cell;
            }

        });
        virtualizedScrollPane = new VirtualizedScrollPane<>(messageList);

        bindEvent();
        ScrollPane scrollPane = new ScrollPane();  // 获取focus
        scrollPane.setContent(virtualizedScrollPane);
        scrollPane.setFitToWidth(true);
        scrollPane.setFitToHeight(true);
        scrollPane.getStyleClass().addAll("noborder-scroll-pane");

        HBox preLoadPane = new HBox(loadPreDataLabel);
        preLoadPane.setAlignment(Pos.CENTER);

        HBox lastLoadPane = new HBox(loadLastDataLabel);
        lastLoadPane.setAlignment(Pos.CENTER);

        VBox vBox = new VBox(preLoadPane, scrollPane, lastLoadPane);
        VBox.setVgrow(scrollPane, Priority.ALWAYS);
        return vBox;
    }

    @Override
    public synchronized boolean addMessage(ImMessage message) {
        if (!canShowMessage(message)) {
            return false;
        }
        items.add(WrapImMessage.wrap(message));
        removeMore(); // 移除过多数据
        messageList.showAsLast(items.size()-1);
        return true;
    }

    @Override
    public synchronized void addMessage(int index, ImMessage message) {
        if (!canShowMessage(message)) {
            return;
        }
        items.add(index, WrapImMessage.wrap(message));
        removeMore(); // 移除过多数据
        messageList.showAsLast(index+1);
    }

    @Override
    public synchronized void addMessages(List<ImMessage> messages) {
        if (messages.size() < 1) return;
        if (!canShowMessage(messages.get(0))) {
            return;
        }
        List<WrapImMessage> wrapImMessages = messages.stream().map(m->WrapImMessage.wrap(m)).filter(wm->!items.contains(wm)).collect(Collectors.toList());
        items.addAll(wrapImMessages);
        removeMore(); // 移除过多数据
//        messageList.show(items.size()-1);
//        messageList.showAsLast(items.size()-1);
        messageList.showAsLast(items.size()-1);
    }

    @Override
    public synchronized ImMessage getFirstMessage() {
        if (sortedList.size() > 0) {
            return sortedList.get(0).getImMessage();
        }
        return null;
    }

    @Override
    public synchronized ImMessage getLastedMessage() {
        if (sortedList.size() > 0) {
            return sortedList.get(sortedList.size() - 1).getImMessage();
        }
        return null;
    }

    @Override
    public synchronized void addFrontMessages(List<ImMessage> messages) {
        if (messages.size() < 1) return;
        if (!canShowMessage(messages.get(0))) {
            return;
        }
        int firstIndex = messageList.getFirstVisibleIndex();
        WrapImMessage showWrapImMessage = sortedList.get(firstIndex);
        List<WrapImMessage> wrapImMessages = messages.stream().map(m->WrapImMessage.wrap(m)).collect(Collectors.toList());
        for (WrapImMessage message : wrapImMessages) {
            if (this.items.contains(message)) {
                System.out.println("error!!!!!!!!!!!");
            }
        }
        items.addAll(0, wrapImMessages);
        removeLastMore(); // 移除过多数据
        int showIndex = sortedList.indexOf(showWrapImMessage);
        messageList.showAsLast(showIndex);
    }

    @Override
    public synchronized void addLastedMessages(List<ImMessage> messages) {
        if (messages.size() < 1) return;
        if (!canShowMessage(messages.get(0))) {
            return;
        }
        int lastedIndex = messageList.getLastVisibleIndex();
        WrapImMessage showWrapImMessage = sortedList.get(lastedIndex);
        List<WrapImMessage> wrapImMessages = messages.stream().map(m->WrapImMessage.wrap(m)).filter(wm->!items.contains(wm)).collect(Collectors.toList());
        items.addAll(wrapImMessages);
        removeMore(); // 移除过多数据

        int showIndex = sortedList.indexOf(showWrapImMessage);
        messageList.show(showIndex);
    }

    @Override
    public synchronized void update(ImMessage message) {
        if (!canShowMessage(message)) {
            return;
        }
        WrapImMessage wrapImMessage = WrapImMessage.wrap(message);
        int idx = items.indexOf(wrapImMessage);
        if (idx < 0) {
            LOGGER.error("更新数据失败, 在列表中找不到这条数据, id: {}, messageServerId: {}", message.getId(), message.getServerId());
            return;
        }
        items.remove(idx);
        if (idx < items.size()) {
            items.add(idx, wrapImMessage );
        } else {
            items.add(wrapImMessage);
        }
        idx = sortedList.indexOf(wrapImMessage);
        if (idx >= 0) {
            messageList.showAsLast(idx);

            if (message.isAttachFile()) {
                MessageReadService.getInstance().asyHandleReadMessage(message.getSessionId(), message.getCurrentUserId(), Arrays.asList(message));
            }
        }
    }

    @Override
    public synchronized void updateMessages(List<ImMessage> messages) {
        List<WrapImMessage> wrapImMessages = new ArrayList<>(messages.size());
        for (ImMessage message : messages) {
            if (!canShowMessage(message)) {
                continue;
            }
            WrapImMessage wrapImMessage = WrapImMessage.wrap(message);
            wrapImMessages.add(wrapImMessage);
        }
        items.forEach(m->{
            for (WrapImMessage wrapImMessage : wrapImMessages) {
                if (m.getImMessage().getId().equals(wrapImMessage.getImMessage().getId())) {
                    m.statusPropertyProperty().set(wrapImMessage.getStatusProperty());
                    m.contentProperty().set(wrapImMessage.getContent());
                    m.progressProperty().set(wrapImMessage.getProgress());
                    m.isProgressProperty().set(wrapImMessage.isProgress());
                    m.selectPropertyProperty().set(wrapImMessage.isSelectProperty());
                }
            }
        });
    }

    @Override
    public ObservableList<WrapImMessage> getItems() {
        return items;
    }

    @Override
    public void setImSession(ImSession imSession) {
        super.setImSession(imSession);
        removeItems(0, getItems().size());
    }

    @Override
    public synchronized void showUp() {
        int idx = messageList.getFirstVisibleIndex();
        if (idx > 0) {
            messageList.showAsLast(idx-1);
        }
    }

    @Override
    public synchronized void showDown() {
        int idx = messageList.getLastVisibleIndex();
        if (idx < getItems().size()-1) {
            messageList.showAsLast(idx+1);
        }
    }

    @Subscribe
    public synchronized void userChange(ImUser imUser) {
        FxThreadUtil.runLater(()->{
            List<WrapImMessage> messages = items.filtered(m->m.getImMessage().getFromUserId().equals(imUser.getUserId()));
            for(WrapImMessage message : messages) {
                int idx = items.indexOf(message);
                if (idx >= 0) {
                    items.remove(idx);
                    if (idx > items.size()) {
                        idx--;
                    }
                    message.getImMessage().setFromImUser(imUser);
                    items.add(idx, message);
                }
            }
        });
    }

    @Subscribe
    public synchronized void groupUserChange(GroupUserNameChange groupUserNameChange) {
        if (!imSession.getId().equals(groupUserNameChange.getSessionId())) return;
        String userId = groupUserNameChange.getUserId();
        FxThreadUtil.runLater(()->{
            List<WrapImMessage> messages = items.filtered(m->m.getImMessage().getFromUserId().equals(userId));
            for(WrapImMessage message : messages) {
                int idx = items.indexOf(message);
                if (idx >= 0) {
                    items.remove(idx);
                    if (idx > items.size()) {
                        idx--;
                    }
                    items.add(idx, message);
                }
            }
        });
    }

    private static void setupUnitIncrement(final ScrollBar bar) {
        bar.unitIncrementProperty().bind(new DoubleBinding() {
            {
                this.bind(new Observable[]{bar.maxProperty(), bar.visibleAmountProperty()});
            }

            protected double computeValue() {
                double max = bar.getMax();
                double visible = bar.getVisibleAmount();
                return max > visible ? 16.0D / (max - visible) * max : 0.0D;
            }
        });
    }

    private void bindEvent() {
        // 鼠标滚动事件
        messageList.addEventHandler(ScrollEvent.SCROLL, e->{
            scrollEvent(e);
        });
//        messageList.estimatedScrollYProperty().addListener((l)->{
//            messageList.estimatedScrollYProperty().ifPresent((v)->{
//                scrollEvent(v);
//            });
//        });
    }

    private synchronized void scrollEvent(ScrollEvent e) {
        if (isLoading) return;
        int firstIndex = messageList.getFirstVisibleIndex();
        int lastIndex = messageList.getLastVisibleIndex();
        WrapImMessage firstMessage = this.getItems().get(firstIndex);
        WrapImMessage lastMessage = this.getItems().get(lastIndex);

        if (e.getDeltaY()>0 && firstMessage.getImMessage().getId().equals(getItems().get(0).getImMessage().getId())) {
            LOGGER.info("show front data firstIdx {}", firstMessage.getImMessage().getId());
            loadFronted();
        }
        if (e.getDeltaY() < 0 && lastMessage.getImMessage().getId().equals(getItems().get(getItems().size()-1).getImMessage().getId())) {
            LOGGER.info("show last data endIdx {}", lastMessage.getImMessage().getId());
            messageList.showAsLast(getItems().size()-1);
            loadLasted();
        }
    }

    private synchronized  void scrollEvent(double v) {
        if (isLoading) return;
        int firstIndex = messageList.getFirstVisibleIndex();
        int lastIndex = messageList.getLastVisibleIndex();
        WrapImMessage firstMessage = this.getItems().get(firstIndex);
        WrapImMessage lastMessage = this.getItems().get(lastIndex);

        if (firstMessage.getImMessage().getId().equals(getItems().get(0).getImMessage().getId()) && !isFrontEnd) {
            LOGGER.info("show front data firstIdx {}", firstMessage.getImMessage().getId());
            loadFronted();
        }
        if (lastMessage.getImMessage().getId().equals(getItems().get(getItems().size()-1).getImMessage().getId()) && !isLastedEnd) {
            LOGGER.info("show last data endIdx {}", lastMessage.getImMessage().getId());
            messageList.showAsLast(getItems().size()-1);
            loadLasted();
        }
    }

    private synchronized void loadLasted() {
        int lastIndex = messageList.getLastVisibleIndex();
        WrapImMessage lastMessage = this.getItems().get(lastIndex);
        if (loadLastCallback != null) {
            loadPreDataLabel.setVisible(false);
            loadLastDataLabel.setVisible(true);
            loadFrontOrLastData(()->loadLastCallback.call(this), lastMessage, false);
        }
    }

    private synchronized void loadFronted() {
        int firstIndex = messageList.getFirstVisibleIndex();
        WrapImMessage firstMessage = this.getItems().get(firstIndex);
        if (loadFrontCallback != null) {
            loadPreDataLabel.setVisible(true);
            loadLastDataLabel.setVisible(false);
            loadFrontOrLastData(()->loadFrontCallback.call(this), firstMessage, true);
        }
    }

    private void loadFrontOrLastData(Supplier<List<WrapImMessage>> supplier, WrapImMessage showWrapMessage, boolean isFront) {
        Task<List<WrapImMessage>> loadTask = new Task() {

            @Override
            protected Object call() throws Exception {
                List<WrapImMessage> newDataList = supplier.get();
                return newDataList;
            }
        };
        isLoading = true;
        ThreadPoolUtil.submitTask(loadTask);
        loadTask.setOnSucceeded(s->{
            List<WrapImMessage> newDataList = loadTask.getValue();
            if (newDataList != null) {
                Set<Long> messageIdSet = getMessageIdSet();
                List<WrapImMessage> filterMessages =
                        newDataList.stream().filter(m->!messageIdSet.contains(m.getImMessage().getId())).collect(Collectors.toList());
                if (isFront) {
                    addFronts(filterMessages);
                } else {
                    addLasts(filterMessages);
                }
            }
            isLoading = false;
            showWrapMessage(showWrapMessage);
//            loadPreDataLabel.setVisible(false);
//            loadLastDataLabel.setVisible(false);

            Node fadeNode = isFront ? loadPreDataLabel : loadLastDataLabel;
            FadeTransition fadeOut = new FadeTransition(Duration.millis(500), fadeNode);
            fadeOut.setFromValue(1); // 从完全透明开始
            fadeOut.setToValue(0);   // 完全不透明结束
            fadeOut.play();
        });
    }

    private synchronized void showWrapMessage(WrapImMessage wrapImMessage) {
        int idx = getItems().indexOf(wrapImMessage);
        if (idx >= 0) {
            messageList.showAsLast(idx);
        }
    }

    public synchronized void addLasts(List<WrapImMessage> datasList) {
        if (datasList.size() < 1) return;
        items.addAll(datasList);
        removeMore();
//        messageList.showAsLast(items.size() - datasList.size());
    }

    public synchronized void addFronts(List<WrapImMessage> datasList) {
        if (datasList.size() < 1) return;
        items.addAll(0, datasList);
        removeLastMore();
//        messageList.showAsLast(datasList.size());
    }

    public void remove(ImMessage message) {
        items.remove(WrapImMessage.wrap(message));
        Cell cell = cellMap.get(message.getClientId());
        if (cell != null) {
            MessageView messageView = (MessageView) cell.getNode();
            messageView.dispose();
        }
    }

    public void clean() {
        items.clear();
        cellMap.forEach((key, cell)-> {
            if (cell != null) {
                MessageView messageView = (MessageView) cell.getNode();
                messageView.dispose();
            }
        });
        cellMap.clear();
    }

    protected synchronized int removeMore() {
        if(getItems().size() > limitTotal) {
            int size = getItems().size() - limitTotal;
            if (size < 10) {
                size = 50;
            }
            removeItems(0, size);
            return size;
        }
        return 0;
    }

    protected synchronized int removeLastMore() {
        int size = getItems().size();
        if(size > limitTotal) {
            int longSize = size - limitTotal;
            if (longSize < 10) {
                longSize = 50;
            }
            int index = size - longSize;
            removeItems(index, size);
            return size - index;
        }
        return 0;
    }

    private void removeItems(int start, int size) {
        getItems().subList(start, size).forEach(wm->{
            Cell cell = cellMap.remove(wm.getImMessage().getClientId());
            if (cell != null) {
                MessageView messageView = (MessageView) cell.getNode();
                messageView.dispose();
                messageView = null;
                cell = null;
            }
        });
        getItems().remove(start, size);
    }
}
